﻿/**
*    Copyright (C) 2008  Chase Kernan 
*    chase.kernan@gmail.com
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
* 
**/

package com.chasekernan.hxnova.utils;

/**
    Assists in building xml structures
**/
class XmlBuilder {
    
    /**
        Returns the underscore_version of a camelCaseWord.
    **/
    public static function getUnderscoreVersion(s : String) : String {
        //just lowercase the first letter and skip it
        
        var num = ~/[0-9]/;
        
        var newString = s.substr(0, 1).toLowerCase() + s.substr(1);
        for(j in 1...newString.length) {
            var char = newString.charAt(j);
            if (num.match(char)) continue;
            
            if(char.toUpperCase() == char) {
                newString = newString.substr(0, j) + "_" + 
                        char.toLowerCase() + 
                        newString.substr(j + 1);
            }
        }
        
        //check last char
        var lastChar = newString.charAt(newString.length - 1);
        
        if (lastChar.toUpperCase() == lastChar && !num.match(lastChar)) {
            newString = newString.substr(0, newString.length - 1) + 
                        "_" + lastChar.toLowerCase();
        }
        
        return newString;
    }
    
    /**
        Returns the camelCaseWord from an underscore_version. If firstUpper, 
        then the firstLetter is capitalized.
    **/
    public static function getCamelCase(s : String, ?firstUpper = false) 
            : String {
        var newString = new String(s);
        if (firstUpper) {
            newString = newString.charAt(0).toUpperCase() + 
                        newString.substr(1);
        }
        
        for(j in 1...newString.length) {
            if(newString.charAt(j) == "_") {
                newString = newString.substr(0, j) + 
                        newString.charAt(j + 1).toUpperCase() + 
                        newString.substr(j + 2);
            }
        }
        
        return newString;
    }
    
    public var xml : Xml;
    
    public function new(title : String) {
        xml = Xml.parse("<" + title + "></" + title + ">").firstChild();
    }
    
    /**
        NOTE: This automatically change the attribute name to an 
        underscore version.
    **/
    public inline function addAttributes(obj : Dynamic, 
                                  attributes : Array < String > ) {
        for (a in attributes) {
            var value = Reflect.field(obj, a);
            var underscore = getUnderscoreVersion(a);
            addAttribute(underscore,  value);
        }
    }
    
    /**
        NOTE: This does not automatically change the attribute name to an 
        underscore version.
    **/
    public function addAttribute(name : String, value : Dynamic) {
        if (value == null) return;
        
        xml.set(name, Std.string(value));
    }
    
    public function addChild(xb : XmlBuilder) {
        xml.addChild(xb.xml);
    }
    
    public function getChild(name : String) : XmlBuilder {
        var xml = xml.elementsNamed(name).next();
        return if (xml == null) null else fromXml(xml);
    }
    
    public static function fromXml(xml : Xml) : XmlBuilder {
        var xb = new XmlBuilder(xml.nodeName);
        xb.xml = xml;
        return xb;
    }
    
    public function addValueChild(name : String, value : Dynamic) {
        if (value == null) return;
        
        xml.addChild(Xml.parse("<" + name + ">" + Std.string(value) +
                               "</" + name + ">")
                .firstChild());
    }
    
    public function addClassObj(obj : Dynamic) {
        if (obj == null) return;
        
        var c = Type.getClass(obj);
        xml.addChild(untyped c.writeToXml(obj));
    }
    
    public function readIntAttribute(name : String) : Null < Int > {
        var uname = getUnderscoreVersion(name);
        if (!xml.exists(uname)) return null;
        return Std.parseInt(xml.get(uname));
    }
    
    public function readFloatAttribute(name : String) : Null < Float > {
        var uname = getUnderscoreVersion(name);
        if (!xml.exists(uname)) return null;
        return Std.parseFloat(xml.get(uname));
    }
    
    public function readStringAttribute(name : String) : String {
        var uname = getUnderscoreVersion(name);
        if (!xml.exists(uname)) return null;
        return xml.get(uname);
    }
    
    public function readBoolAttribute(name : String) : Null < Bool > {
        var uname = getUnderscoreVersion(name);
        if (!xml.exists(uname)) return null;
        return xml.get(uname) == "true";
    }
    
    public function readClassObj(c : Class < Dynamic > ) : Dynamic {
        if (!xml.elementsNamed(untyped c.XML_TITLE).hasNext()) return null;
        
        return untyped c.readFromXml(getChild(c.XML_TITLE).xml);
    }
    
    public function readValueChild(name : String) : String {
        if (!xml.elementsNamed(name).hasNext()) return null;
        
        return xml.elementsNamed(name).next().firstChild().nodeValue;
    }
    
}

#if unit

class XmlBuilderTest extends haxe.unit.TestCase {
    
    var xb : XmlBuilder;
    
    public function new() {
        super();
    }
    
    override public function setup() {
        xb = new XmlBuilder("test");
    }
    
    public function testGetUnderscoreVersion() {
        assertEquals("testing_first_second", 
                     XmlBuilder.getUnderscoreVersion("TestingFirstSecond"));
        assertEquals("foo_b_a_r", 
                     XmlBuilder.getUnderscoreVersion("fooBAR"));
    }
    
    public function testGetCamelCase() {
        assertEquals("testingFirstSecond", 
                     XmlBuilder.getCamelCase("testing_first_second"));
        assertEquals("fooBAR",
                     XmlBuilder.getCamelCase("foo_b_a_r"));
        assertEquals("FirstCap",
                     XmlBuilder.getCamelCase("first_cap", true));
    }
    
    public function testNew() {
        assertEquals("test", xb.xml.nodeName);
    }
    
    public function testAddAttributes() {
        var obj = {testOne : 5, testTwo : "hello", testThree : 4.5};
        var attribs = ["testOne", "testTwo", "testThree"];
        xb.addAttributes(obj, attribs);
        
        for (attrib in attribs) {
            assertTrue(xb.xml.exists(XmlBuilder.getUnderscoreVersion(attrib)));
        }
        
        assertEquals("5", xb.xml.get("test_one"));
        assertEquals("hello", xb.xml.get("test_two"));
    }
    
    public function testAddAttribute() {
        xb.addAttribute("test_one", 56);
        assertEquals("56", xb.xml.get("test_one"));
    }
    
    public function testAddChild() {
        xb.addChild(new XmlBuilder("test2"));
        assertEquals("test2", xb.xml.firstElement().nodeName);
    }
    
    public function testGetChild() {
        xb.addChild(new XmlBuilder("test2"));
        assertEquals("test2", xb.getChild("test2").xml.nodeName);
    }
    
    public function testFromXml() {
        xb = XmlBuilder.fromXml(Xml.parse("<testing></testing>").firstChild());
        assertEquals("testing", xb.xml.nodeName);
    }
    
    public function testAddValueChild() {
        xb.addValueChild("child", 45);
        assertEquals("45", xb.getChild("child").xml.firstChild().nodeValue);
        
        xb.addValueChild("child2", null);
        assertEquals(null, xb.getChild("child2"));
    }
    
    public function testAddClassObj() {
        xb.addClassObj(new TestClass());
        
        assertEquals("test_class", xb.getChild("test_class").xml.nodeName);
    }
    
    public function testReadIntAttribute() {
        xb.addAttribute("int", 5);
        assertEquals(5, xb.readIntAttribute("int"));
        assertEquals(null, xb.readIntAttribute("foo"));
    }
    
    public function testReadFloatAttribute() {
        xb.addAttribute("float", 5.5);
        assertEquals(5.5, xb.readFloatAttribute("float"));
        assertEquals(null, xb.readFloatAttribute("foo"));
    }
    
    public function testReadStringAttribute() {
        xb.addAttribute("string", "test");
        assertEquals("test", xb.readStringAttribute("string"));
        assertEquals(null, xb.readStringAttribute("foo"));
    }
    
    public function testReadBoolAttribute() {
        xb.addAttribute("bool", false);
        assertEquals(false, xb.readBoolAttribute("bool"));
        assertEquals(null, xb.readBoolAttribute("foo"));
    }
    
    public function testReadClassObj() {
        xb.addClassObj(new TestClass());
        assertTrue(xb.readClassObj(TestClass) != null);
    }
    
    public function testReadValueChild() {
        xb.addValueChild("test_value", "test");
        assertEquals("test", xb.readValueChild("test_value"));
        assertEquals(null, xb.readValueChild("foo"));
    }
}

class TestClass {
    public function new() {
        
    }
    
    public static var XML_TITLE = "test_class";
    
    public static function writeToXml(tc : TestClass) : Xml {
        return Xml.parse("<test_class></test_class>").firstChild();
    }
    
    public static function readFromXml(xml : Xml) : TestClass {
        return new TestClass();
    }
}

#end